/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/interpret/StackOfNames.h"
#include "simodo/interpret/AnalyzeException.h"

#include "simodo/inout/format/fmt.h"

#include <cassert>

namespace simodo::interpret
{
    name_index_t StackOfNames::push(const variable::Variable & name)
    {
        name_index_t new_name_index = _stack.size();
        _stack.push(name);
        return new_name_index;
    }

    name_index_t StackOfNames::pushRef( const variable::Variable & v, 
                                        const inout::TokenLocation & location,
                                        const variable::VariableSet_t & spec)
    {
        const variable::Variable & o = v.origin();

        return push({ o.name(), variable::VariableRef {o}, location, spec });
    }

    name_index_t StackOfNames::top(size_t depth) const
    {
        if (depth >= _stack.size())
            throw bormental::DrBormental("StackOfNames::top", 
                        inout::fmt("Attempt to handle invalid offset %1 in stack of variables (actual size = %2)")
                        .arg(depth)
                        .arg(_stack.size()));

        return _stack.size() - depth - 1;
    }

    name_index_t StackOfNames::index_over_top() const 
    {
        return _stack.size();
    }

    void StackOfNames::pop(size_t depth)
    {
        _stack.popAmount(depth);
    }

    void StackOfNames::erase(name_index_t name_index)
    {
        assert(name_index < _stack.size());
        _stack.stack().erase( _stack.stack().begin()+name_index);
    }

    name_index_t StackOfNames::find(const std::u16string & name, BoundScope scope) const
    {
        assert(!_boundaries.empty());

        // Ищем с конца стека
        name_index_t     i  = _stack.size()-1;
        boundary_index_t bi = _boundaries.size()-1;
        for( ; i < _stack.size(); --i) {
            // Локальные границы могут дублироваться, поэтому проверяем, пока совпадает
            for( ; _boundaries[bi].ref_to_stack > i; --bi)
                if (scope == BoundScope::Local)
                    return UNDEFINED_NAME_INDEX;
                else if (_boundaries[bi].type == BoundScope::Global)
                    return findGlobal(name);

            if (_stack.at(i).name() == name)
                return i;
        }

        return findGlobal(name);
    }

    variable::Variable & StackOfNames::variable(name_index_t name_index)
    {
        return _stack.at(name_index);
    }

    const variable::Variable & StackOfNames::variable(name_index_t name_index) const
    {
        return _stack.at(name_index);
    }

    void StackOfNames::setName(name_index_t name_index, const std::u16string & new_name)
    {
        assert(name_index < _stack.size());
        _stack.at(name_index).setName(new_name);
    }

    bool StackOfNames::addGlobal(name_index_t name_index)
    {
        assert(name_index < _stack.size());

        /// \todo Пока сделано так, чтобы глобальные имена (контракты) добавлялись только из файла 
        /// инициализации контрактов. Иначе пришлось бы каждый раз при сносе границ проверять глобальные 
        /// имена за этими границами.
        if (_boundaries.size() > 2)
            return false;

        for(size_t i=0; i < _globals.size(); ++i)
            if (_globals[i] == name_index || variable(_globals[i]).name() == variable(name_index).name())
                return false;

        _globals.push_back(name_index);
        return true;
    }

    boundary_index_t StackOfNames::startBoundary(BoundScope type, boundary_mark_t mark)
    {
        boundary_index_t bi = _boundaries.size();
        _boundaries.push_back({type, _stack.size(), mark, {}});
        return bi;
    }

    void StackOfNames::setBoundaryToGlobal(boundary_index_t bound_index)
    {
        assert(bound_index < _boundaries.size());

        BoundInfo & bound = _boundaries[bound_index];

        bound.type = BoundScope::Global;
    }

    bool StackOfNames::clearBoundary(boundary_index_t bound_index)
    {
        if (bound_index >= _boundaries.size())
            return false;

        /// \todo Тут уместно вызывать деструкторы переменных на стеке. 

        size_t bounds_count    = _boundaries.size() - bound_index;
        size_t variables_count = _stack.size() - _boundaries[bound_index].ref_to_stack;

        _stack.popAmount(variables_count);

        while(bounds_count--) _boundaries.pop_back();

        return true;
    }

    std::pair<name_index_t,name_index_t> StackOfNames::boundaryLowAndTop(boundary_index_t bound_index) const
    {
        assert(bound_index < _boundaries.size());

        name_index_t top_index = (bound_index == _boundaries.size()-1) 
                               ? _stack.size()
                               : _boundaries[bound_index+1].ref_to_stack;

        return {_boundaries[bound_index].ref_to_stack, top_index};
    }

    boundary_index_t StackOfNames::findNearestMarkedBoundary(boundary_mark_t mark, BoundScope type) const
    {
        for(boundary_index_t i=_boundaries.size()-1; i < _boundaries.size(); --i) {
            if (_boundaries[i].mark == mark)
                return i;

            if (type == BoundScope::Local && _boundaries[i].type == BoundScope::Global)
                return UNDEFINED_BOUNDARY_INDEX;
        }

        return UNDEFINED_BOUNDARY_INDEX;
    }

    std::pair<boundary_mark_t,BoundScope> StackOfNames::getTopBoundaryMarkAndScope() const
    {
        assert(!_boundaries.empty());

        return {_boundaries.back().mark, _boundaries.back().type};
    }

    variable::VariableSetWrapper_mutable StackOfNames::makeVariableSetWrapper()
    {
        assert(!_boundaries.empty());

        size_t ref_to_args { _boundaries.back().ref_to_stack };

        return { _stack.stack(), ref_to_args };
    }

    std::shared_ptr<variable::Object> StackOfNames::convertToObject(boundary_index_t bound_index)
    {
        variable::VariableSet_t vars;

        auto [low_index, top_index] = boundaryLowAndTop(bound_index);

        for(name_index_t i = low_index; i < top_index; ++i) {
            assert(!_stack.at(i).name().empty());
            vars.push_back(_stack.at(i));
        }

        return std::make_shared<variable::Object>(vars);
    }

    void StackOfNames::fillFromObject(std::shared_ptr<variable::Object> obj)
    {
        for(const variable::Variable & v : obj->variables())
            push(v.copyVariable());
    }

    bool StackOfNames::isRecursiveCalls() const
    {
        assert(!_boundaries.empty());

        if (_boundaries.back().mark != BOUNDARY_MARK_FUNCTION_FRAME)
            return false;

        name_index_t function_index = _boundaries.back().ref_to_stack-1;

        assert(function_index < _stack.size());

        const variable::Variable & function = _stack.at(function_index);

        if (!function.origin().value().isFunction())
            return false;

        const std::shared_ptr<variable::Object> function_object = function.origin().value().getFunction();

        assert(function_object);

        for(boundary_index_t i = _boundaries.size()-2; i < _boundaries.size(); --i)
            if (_boundaries[i].mark == BOUNDARY_MARK_FUNCTION_FRAME) {
                name_index_t               function_index = _boundaries[i].ref_to_stack-1;
                const variable::Variable & function       = _stack.at(function_index);

                if (function.origin().value().isFunction()
                 && function.origin().value().getFunction().get() == function_object.get())
                    return true;
            }
        
        return false;
    }

    std::vector<boundary_index_t> StackOfNames::getFunctionBounds() const
    {
        std::vector<boundary_index_t> bounds;

        for(boundary_index_t i = _boundaries.size()-1; i < _boundaries.size(); --i)
            if (_boundaries[i].mark == BOUNDARY_MARK_FUNCTION_FRAME)
                bounds.push_back(i);

        boundary_index_t start_index = bounds.empty() ? _boundaries.size()-1 : bounds.back()-1;

        for(boundary_index_t i = start_index; i < _boundaries.size(); --i)
            if (_boundaries[i].type == BoundScope::Global) {
                bounds.push_back(i);
                break;
            }

        return bounds;
    }

    std::vector<name_index_t> StackOfNames::getFrameLocals(boundary_index_t frame_index) const
    {
        boundary_index_t upper_frame = 0;

        for(boundary_index_t i = frame_index+1; i < _boundaries.size(); ++i)
            if (_boundaries[i].mark == BOUNDARY_MARK_FUNCTION_FRAME) {
                upper_frame = i;
                break;
            }

        name_index_t bottom_name = _boundaries[frame_index].ref_to_stack;
        name_index_t upper_name;

        if (upper_frame == 0)
            upper_name = _stack.size();
        else
            upper_name = _boundaries[upper_frame].ref_to_stack;

        std::vector<name_index_t> locals;

        for(name_index_t i = upper_name - 1; i >= bottom_name && i < _stack.size(); --i) {
            const variable::Variable & v = variable(i);
            if (v.type() != variable::ValueType::Ref && !v.name().empty())
                locals.push_back(i);
        }

        return locals;
    }

    name_index_t StackOfNames::findGlobal(const std::u16string & name) const
    {
        for(name_index_t i : _globals)
            if (variable(i).name() == name)
                return i;

        return UNDEFINED_NAME_INDEX;
    }

}
